#include <stdio.h>
#include <math.h>
#include <iostream>
#include <string.h>
#include <dirent.h>
#include <time.h>
#include <pthread.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>

#include "opencv2/core/core.hpp"
#include "opencv2/nonfree/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/calib3d/calib3d.hpp"

#define IMAGE_LIBRARY_SIZE 2725
#define N_CORES 8

using namespace cv;

double distance(double,double,double,double);
double area(double, double, double);
double doHomo(int);
double initObjectVectors(int);
int initObject(char*);
int initScene(char*);
void serve(char*);
void *doHomoThread(void*);

//cv global varibles
const int minHessian = 400;
double sceneSize;
SurfFeatureDetector detector( minHessian );
SurfDescriptorExtractor extractor;
FlannBasedMatcher matcher;

//scene global variables
Mat scene;
std::vector<KeyPoint> keypoints_scene;
Mat descriptors_scene;

//object global variables
char imageNames[IMAGE_LIBRARY_SIZE][256];
Mat object[IMAGE_LIBRARY_SIZE];
std::vector<KeyPoint> keypoints_object[IMAGE_LIBRARY_SIZE];
Mat descriptors_object[IMAGE_LIBRARY_SIZE];

	int main( int argc, char** argv ){

		if( argc != 2 )	{
			return -1; 
		}
		
		char objectDir[strlen(argv[1])];
		strcpy(objectDir, argv[1]);
	
		initObject(objectDir);

		string python_message = "";
		char sceneName[256];
		bool quit = false;
		
		printf("Server is up \n");
		while (!quit)
		{
			std::cin >> python_message;  
			if ( python_message == "quit"){
				quit = true;
			}else{
				strcpy(sceneName,python_message.c_str());
				printf("%s\n",sceneName);
				serve(sceneName);   
			}		   
		} 
		
	  
		return 0;
    }
	
	int initScene(char* sceneName){
		//calculate the scene matrix, size, keypoints vector,descriptor matrix
		scene = imread( sceneName, CV_LOAD_IMAGE_GRAYSCALE );
		if( !scene.data ){ 
			std::cout<< " --(!) Error reading scene image::name "<<sceneName << std::endl; return -1;
		}
		sceneSize = scene.size().area();
		detector.detect( scene, keypoints_scene );
		extractor.compute( scene, keypoints_scene, descriptors_scene );	
		return 0;
	}


	int initObject(char* objectDir){
		//debug:: first timer
		time_t timer1,timer2;
		time(&timer1);
		
		//interate throughout the given directory
		DIR *dir;
		struct dirent *ent;
		char* objectPath;

		if ((dir = opendir (objectDir)) != NULL) {
	
			int i = 0;
		
			while (((ent = readdir (dir)) != NULL)) {
	  
				//rule out the invalid image names
				int len = strlen(ent->d_name);
				if(len <= 4) continue;
				if(ent->d_name[len-1]!='g') continue;
				if(ent->d_name[len-2]!='p') continue;
				if(ent->d_name[len-3]!='j') continue;
				if(ent->d_name[len-4]!='.') continue;
			
				//i is the index for images
				i++;
		
				//assign String object with parameter, strcat with rewrite first parameter
				strcpy(objectPath, objectDir);
				strcat(objectPath, ent->d_name);

				//test pic "../BestbuyProducts/images/6.jpg" 0.756134
				//../BestbuyProducts/images/2121079_rc.jpg
				//calulate the object iamge matrix
				object[i] = imread( objectPath, CV_LOAD_IMAGE_GRAYSCALE );

				if( !object[i].data){ 
					std::cout<< " --(!) Error reading object images ::name "<< objectPath << std::endl; 
					return -1;
				}
				strcpy(imageNames[i],ent->d_name);
				
				initObjectVectors(i);
        }
			//close the directory
			closedir (dir);
			
			//debug:: timer program ouputs here
			time(&timer2);
			printf("%f seconds elapsed during init\n",difftime(timer2,timer1));
			return 0;
		} else {
		  return -1;
		}

	}

	void serve(char* sceneName){
		
		time_t timer1,timer2;
		time(&timer1);
		pthread_t thread[N_CORES+1];
		
		if (initScene(sceneName)==-1){
			return;
		}
		
		double para[N_CORES+1][4];
		for(int i = 1;i <= N_CORES; i++){
			para[i][0] = (i-1)*(IMAGE_LIBRARY_SIZE/N_CORES)+1;
			para[i][1] =  i*(IMAGE_LIBRARY_SIZE/N_CORES);
		}
		
		for(int i = 1;i <= N_CORES; i++){
			pthread_create( &thread[i], NULL, doHomoThread, (void*) para[i]);
		}
		
		for(int i = 1;i <= N_CORES; i++){
			pthread_join( thread[i], NULL);
		}
/*		
		//thread1
		double para1[4];
		para1[0] = 1;
		para1[1] = IMAGE_LIBRARY_SIZE/2;
		
		//thread2
		double para2[4];
		para2[0] = IMAGE_LIBRARY_SIZE/2+1;
		para2[1] = IMAGE_LIBRARY_SIZE;
		
		//start threads
		pthread_create( &thread1, NULL, doHomoThread, (void*) para1);
		pthread_create( &thread2, NULL, doHomoThread, (void*) para2);
		
		pthread_join( thread1, NULL);
		pthread_join( thread2, NULL);
*/	
		//combine return values from threads
		double min = 100;
		char* name;
		int pos = 1;
		
		for(int i = 1;i <= N_CORES; i++){
			if(min > para[i][2]){
				min = para[i][2];
				pos = (int) para[i][3];
			}
		}
/*		
		double min1 = para1[2];
		double min2 = para2[2];
		int pos1 = (int) para1[3];
		int pos2 = (int) para2[3];
		if(min1 < min2){
			min = min1;
			name = imageNames[pos1];
		} else {
			min = min2;
			name = imageNames[pos2];
		}
*/
		name = imageNames[pos];
	
		//final result
		if(min<100) {
			printf("The image named %s has a minimum distance of %f\n", name, min);
		} else {
			printf("There is not match in the store's database");
		}
		

		
		//debug:: comparision time
		time(&timer2);
		printf("%f seconds elapsed during serve\n",difftime(timer2,timer1));
		
	}
	
	void *doHomoThread( void *ptr ){
		 int from,to;
		 double* para = (double *) ptr;
		 from = (int) para[0];
		 to = (int) para[1];
		 
		 double rate, min = 100;
		 int pos = from;
		 for(int j = from;j < to;j++){
			
//			printf("%dth object name:%s\n",j,imageNames[j]);
//			printf("   keypoint size:%u\n",(unsigned)keypoints_object[j].size());
			
			//record the file name with largest similarity
			rate = doHomo(j);

			//debug:: print out similarity and file name for each image
			//printf("%d: %f : %s\n", i, rate, ent->d_name );
			
			if(rate >= 0 && rate < min) {
				min = rate;
				pos = j;
			}
			
		}
		
		para[2] = min;
		para[3] = pos;
		return ptr;
	}
	
	double doHomo(int i){
//		printf("Matching the %dth object -->",i);
		
		//Create matches
		std::vector< DMatch > matches;
		matcher.match( descriptors_object[i], descriptors_scene, matches );
		
		//-- Quick calculation of max and min distances between keypoints
		double max_dist = 0; 
		double min_dist = 100;

		for( int j = 0; j < descriptors_object[i].rows; j++ ){ 
			double dist = matches[j].distance;
			if( dist < min_dist ) min_dist = dist;
			if( dist > max_dist ) max_dist = dist;
		}

//		printf("min is %f\n", min_dist);
//		printf("max is %f\n", max_dist);
		
		std::vector< DMatch > good_matches;
		for( int j = 0; j < descriptors_object[i].rows; j++ ){
			if( matches[j].distance <= 3*min_dist ){
				good_matches.push_back( matches[j]); 
			}
		}
		
//		printf("good match size is %u\n", (unsigned)good_matches.size());
		if(good_matches.size()<4) return -1;
		
		//-- Localize the object
		std::vector<Point2f> objectPoints;
		std::vector<Point2f> scenePoints;
		
		for( int j = 0; j < good_matches.size(); j++ ){
			//-- Get the keypoints from the good matches
			objectPoints.push_back( keypoints_object[i][ good_matches[j].queryIdx ].pt );
			scenePoints.push_back( keypoints_scene[ good_matches[j].trainIdx ].pt );
		}
		
		Mat H = findHomography( objectPoints, scenePoints, CV_RANSAC );

		//-- Get the corners from the scene
		static std::vector<Point2f> obj_corners(4);
		obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( object[i].cols, 0 );
		obj_corners[2] = cvPoint( object[i].cols, object[i].rows ); obj_corners[3] = cvPoint( 0, object[i].rows );
		std::vector<Point2f> scene_corners(4);
		
		perspectiveTransform( obj_corners, scene_corners, H);
		
		double a = distance(scene_corners[0].x,scene_corners[0].y,scene_corners[1].x,scene_corners[1].y);
		double b = distance(scene_corners[1].x,scene_corners[1].y,scene_corners[2].x,scene_corners[2].y);
		double c = distance(scene_corners[2].x,scene_corners[2].y,scene_corners[0].x,scene_corners[0].y);
		double d = distance(scene_corners[2].x,scene_corners[2].y,scene_corners[3].x,scene_corners[3].y);
		double e = distance(scene_corners[3].x,scene_corners[3].y,scene_corners[0].x,scene_corners[0].y);
		double f = distance(scene_corners[3].x,scene_corners[3].y,scene_corners[1].x,scene_corners[1].y);
		double g = area(a,b,c)+area(d,e,c);
		double g1 = area(a,f,e)+area(b,d,f);
		double h = g/(double)sceneSize;
//		printf("%f,%f,%f\n",g,g1,h);
		if((h>0.4)&&((h<1)||abs(h-1)<0.000001)&&abs(g - g1)< 0.1){
			return min_dist;
		} else {
			return -1;
		}
	}
	
	double initObjectVectors(int i){
//		printf("initializing the %dth object\n",i);
		detector.detect( object[i], keypoints_object[i]);
		extractor.compute( object[i], keypoints_object[i], descriptors_object[i] );
		return 0;
	}
	
	
	double distance(double x1,double y1,double x2,double y2){
		return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
	}

	double area(double a, double b, double c){
		double s = (a + b + c)/2;

		return sqrt(s*(s-a)*(s-b)*(s-c));

	}
